classdef (CaseInsensitiveProperties = true) sp_data < hgsetgetplus
    
    properties (SetObservable, SetAccess = {?sp_data})
        x
        y
        e
    end
    
    properties (SetObservable)
        fitting
        plotting
        xLabel
        yLabel
        name
        identifier
    end
    
    properties (Hidden, SetObservable)
        line_hand
        ax_hand
        position
    end
    
    properties (Hidden, GetAccess={?sp_data}, SetAccess={?sp_data},SetObservable)
        xBackup
        yBackup
        eBackup
        lis
    end
    
    methods
        function obj = sp_data(varargin)
            % ValidateData listeners must be added AFTER copy!
            if nargin == 0
                obj.fitting = sp_fit();
                obj.plotting = sp_plot();
                obj.lis=approveListener(obj);
            end
            
            if nargin == 1
                firsArg=varargin{1};
                if isa(firsArg,'sp_data')
                    obj=firsArg.copy;
                    set(obj,'lis',approveListener(obj));
                end
                
                if isa(firsArg,'struct')
                    obj=sp_data;
                    p=properties(obj);
                    for i=1:length(p)
                        obj.(p{i}) = firsArg.(p{i});
                    end
                end
            end
            
            if nargin == 3
                obj.x=varargin{1};
                obj.y=varargin{2};
                obj.e=varargin{3};
                obj.fitting = sp_fit();
                obj.plotting = sp_plot();
                validateData(obj);
                obj.lis=approveListener(obj);
            end
            
        end
        
        function validateData(obj,varargin)
            if any(imag(get(obj,'x')))
                error('sp_data:ImaginaryData','The contents of x must be real')
            end
            if any(imag(get(obj,'y')))
                error('sp_data:ImaginaryData','The contents of y must be real')
            end
            if any(imag(get(obj,'e')))
                error('sp_data:ImaginaryData','The contents of e must be real')
            end
            
            if isrow(get(obj,'x')) && length(get(obj,'x'))>1
                set(obj,'x',get(obj,'x')')
            end
            if isrow(get(obj,'y'))&& length(get(obj,'y'))>1
                set(obj,'y',get(obj,'y')')
            end
            if length(get(obj,'e')) == 1 && length(get(obj,'x'))>1
                set(obj,'e',get(obj,'e')*ones(size(get(obj,'x'))))
            end
            if isrow(get(obj,'e')) && length(get(obj,'x'))>1
                set(obj,'e',get(obj,'e')')
            end
            if  ((length(get(obj,'x'))~=length(get(obj,'y'))) || ...
                    (length(get(obj,'y'))~=length(get(obj,'e'))) || ...
                    (length(get(obj,'x'))~=length(get(obj,'e'))))
                restoreV(obj)
                error('sp_data:SizeMismatch','The size of the dataset is invalid. Previous values have been restored.');
            end
        end
        
        function setData(obj,varargin)
            l=get(obj,'lis');
            for i=1:3
                l(i).Enabled = false;
            end
            set(obj,'x',varargin{1})
            set(obj,'y',varargin{2})
            set(obj,'e',varargin{3})
            validateData(obj)
            for i=1:3
                l(i).Enabled = true;
            end
        end
        
        function [xout, yout, eout] = getData(obj)
           xout = get(obj,'x');
           yout = get(obj,'y');
           eout = get(obj,'e');
        end
        
        function [X, I, varargout] = getBinnedData1D(obj,varargin)
            xx = get(obj,'x');
            z  = get(obj,'y');
            
            if isempty(varargin)
                % Create bin ranges
                % This is a fencepost problem, hence the + 1!
                x_res = ((max(xx)-min(xx))*50);
                xbin = linspace(min(xx), max(xx), ((max(xx)-min(xx))/x_res) + 1);
            elseif length(varargin) == 1
                xbin = varargin{1}(:)';
            else
                error('spectra:binDataBins',...
                    'In order to bin data x1 must be supplied.')
            end
            
            [X, ind] = histc(xx, xbin);
            I = accumarray(ind(:),z,[],@sum);
            
            if nargout == 3
                ee = get(obj,'e');
                % Simple sun of errors
                varargout{1} = accumarray(ind(:),ee,[],@(x) sum(x.^2)^0.5);
            end
            
        end
        
        function binData1D(obj,varargin)
            [X, I, E] = getBinnedData1D(obj, varargin{:});
            obj.setData(X, I, E);
        end
        
        function [X, I, varargout] = getCombinedData1D(obj,varargin)
            
            xx = get(obj,'x');
            
            if length(varargin) == 1
                toll = varargin{1};
            else
                toll=min(diff(xx))/10;
                warning('sp_data:combineBin',...
                    'Tollerance for combining data has not been given. Using %s',...
                    num2str(toll))
            end
            
%             xbin = (toll*floor(min(xx)/toll)):toll:((toll*ceil(max(xx)/toll))+toll);
            xbin = linspace(toll*floor(min(xx)/toll),toll*ceil(max(xx)/toll)+toll,(ceil(max(xx)/toll)-floor(min(xx)/toll))+1);
            
            z  = get(obj,'y');
            ee = get(obj,'e');
                        
            [~, ind] = histc(xx, xbin);
            X = accumarray(ind(:), xx, [], @mean);
            I = accumarray(ind(:), z , [], @mean);
            E = accumarray(ind(:), ee, [], @(x) (sum(x.^2)^0.5)/length(x));
% 
%             X = accumarray_f(xx,ind,@mean);
%             I = accumarray_f( z,ind,@mean);
%             E = accumarray_f(ee,ind, @(x) (sum(x.^2)^0.5)/length(x));
            
            ind1 = X ~= 0;
            ind2 = I ~= 0;
            ind3 = E ~= 0;
            
            X = X(ind1 & ind2 & ind3);
            I = I(ind1 & ind2 & ind3);
            if nargout == 3
                varargout{1} = E(ind1 & ind2 & ind3);
            end
            
        end
        
        function combineData1D(obj,varargin)
            [X, I, E] = getCombinedData1D(obj, varargin{:});
            obj.setData(X, I, E);
        end  
    end
    
    methods (Hidden = true, Access={?sp_data})
        
        function l = approveListener(obj)
            l(1)  = addlistener(obj,'x','PostSet',@sp_data.changeV);
            l(2)  = addlistener(obj,'y','PostSet',@sp_data.changeV);
            l(3)  = addlistener(obj,'e','PostSet',@sp_data.changeV);
            l(4)  = addlistener(obj,'x','PreSet',@sp_data.backupV);
            l(5)  = addlistener(obj,'y','PreSet',@sp_data.backupV);
            l(6)  = addlistener(obj,'e','PreSet',@sp_data.backupV);
            l(7)  = addlistener(obj,'xLabel','PostSet',@sp_data.changeV);
            l(8)  = addlistener(obj,'yLabel','PostSet',@sp_data.changeV);
            l(9)  = addlistener(obj,'name','PostSet',@sp_data.changeV);
            l(10) = addlistener(obj,'ax_hand','PostSet',@sp_data.changeV);
        end
        
        function restoreV(obj,varargin)
            if isempty(varargin)
                set(obj,'x',get(obj,'xBackup'));
                set(obj,'y',get(obj,'yBackup'));
                set(obj,'e',get(obj,'eBackup'));
            else
                set(obj,varargin{1},get(obj,[varargin{1} 'Backup']));
            end
        end
        
    end
    
    
    methods (Static, Hidden=true, Access={?sp_data})
        function changeV(src,evnt)
            switch lower(src.Name)
                case {'x', 'y', 'e'}
                    validateData(evnt.AffectedObject,src.Name)
                case {'ylabel'}
                    h = evnt.AffectedObject.ax_hand;
                    if ishandle(h)
                        ylabel(h,evnt.AffectedObject.(src.Name))
                    end
                case {'xlabel'}
                    h = evnt.AffectedObject.ax_hand;
                    if ishandle(h)
                        xlabel(h,evnt.AffectedObject.(src.Name))
                    end
                case {'ax_hand'}
                    h = evnt.AffectedObject.ax_hand;
                    if ishandle(h)
                        xlabel(h,get(evnt.AffectedObject,'XLabel'))
                        ylabel(h,get(evnt.AffectedObject,'YLabel'))
                        title(h,get(evnt.AffectedObject,'Name'))
                    end
                case {'name'}
                    set(evnt.AffectedObject.plotting,'DisplayName',evnt.AffectedObject.(src.Name))
            end
        end
        
        function backupV(src,evnt)
            switch src.Name
                case {'x','y','e'}
                    set(evnt.AffectedObject,[src.Name 'Backup'],evnt.AffectedObject.(src.Name))
            end
        end
        
    end
    
end
